package be.pluyckx.batteryprogress.utils;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

/**
 * Created by Philip on 15/09/13.
 * <p/>
 * The array is always stored SORT_ASC. Specifying a sort of SORT_DESC just converts the mIndex you
 * pass to the other end of the array, so it seems that the array is sorted DESC.
 */
public class SortedArray<E extends Comparable<E>> implements Iterable<E> {
  public static final int SORT_ASC = 1;
  public static final int SORT_DESC = 2;

  private Object[] mData;
  private int mSize;

  public SortedArray() {
    this(10);
  }

  public SortedArray(int capacity) {
    mData = new Object[capacity];
    mSize = 0;
  }

  public SortedArray(SortedArray<E> data) {
    mData = new Object[data.mData.length];
    mSize = data.mSize;

    for (int i = 0; i < mSize; i++) {
      mData[i] = data.mData[i];
    }
  }

  public int size() {
    return mSize;
  }

  /**
   * Add an item to the sorted array.
   *
   * @param item
   * @return
   */
  public int add(E item) {
    ensureCapacity(mSize + 1);

    int index = mSize - 1;
    while (index >= 0 && ((Comparable<E>) mData[index]).compareTo(item) > 0) {
      mData[index + 1] = mData[index];
      index--;
    }

    mData[index + 1] = item;
    mSize++;

    return index + 1;
  }

  public void addAll(Collection<E> collection) {
    ensureCapacity(mSize + collection.size());

    for (E e : collection) {
      add(e);
    }
  }

  public void clear() {
    for (int i = 0; i < mSize; i++) {
      mData[i] = null;
    }

    mSize = 0;
  }

  /**
   * Get an item at the mIndex. Equals to the call {@code get(mIndex, SORT_ASC)}.
   *
   * @param index
   * @return
   */
  public E get(int index) {
    return get(index, SORT_ASC);
  }

  /**
   * Get an item at the mIndex.
   *
   * @param index
   * @param sort  SORT_ASC or SORT_DESC
   * @return
   */
  public E get(int index, int sort) {
    if (sort == SORT_ASC) {
      return (E) mData[index];
    } else {
      return (E) mData[mSize - 1 - index];
    }
  }


  /**
   * Remove the item at the mIndex. Equals to the call {@code remove(mIndex, SORT_ASC)}.
   *
   * @param index
   * @return
   */
  public E remove(int index) {
    return remove(index, SORT_ASC);
  }

  /**
   * Remove the item at the mIndex.
   *
   * @param index
   * @param sort  SORT_ASC or SORT_DESC
   * @return
   */
  public E remove(int index, int sort) {
    if (index > mSize) {
      return null;
    }

    E tmp = (E) mData[index];

    for (int i = index; i < mSize - 1; i++) {
      mData[i] = mData[i + 1];
    }

    mData[mSize] = null;
    mSize--;

    return tmp;
  }

  /**
   * Remove the item.
   *
   * @param item
   * @return
   */
  public E remove(E item) {
    int index = indexOf(item);
    if (index < 0) {
      return null;
    }

    return remove(index, SORT_ASC);
  }

  public int indexOf(Object item) {
    return indexOf(item, SORT_ASC);
  }

  /**
   * Get the mIndex of an item.
   *
   * @param item
   * @param sort SORT_ASC or SORT_DESC
   * @return Negative when not found, otherwise positive.
   */
  public int indexOf(Object item, int sort) {
    int index = Arrays.binarySearch(mData, 0, mSize, item);

    if (sort == SORT_ASC) {
      return index;
    } else {
      return mSize - 1 - index;
    }
  }

  private void ensureCapacity(int capacity) {
    if (mData.length < capacity) {
      expand((capacity * 3) / 2 + 1);
    }
  }

  private void expand(int capacity) {
    if (capacity <= mData.length) {
      return;
    }
    Object[] tmp = new Object[capacity];

    for (int i = 0; i < mSize; i++) {
      tmp[i] = mData[i];
    }

    mData = tmp;
  }

  /**
   * Creates an iterator in ASC mode
   *
   * @return
   */
  @Override
  public Iterator<E> iterator() {
    return iterator(SORT_ASC);
  }

  /**
   * Creates an iterator in ASC mode
   *
   * @param sort SORT_ASC or SORT_DESC
   * @return
   */
  public Iterator<E> iterator(int sort) {
    return new SortedArrayIterator(sort);
  }

  private class SortedArrayIterator implements Iterator<E> {
    private int mIndex;
    private int mCheckSize;
    private int mSort;

    public SortedArrayIterator() {
      this(SORT_ASC);
    }

    public SortedArrayIterator(int sort) {
      if (sort == SORT_ASC) {
        mIndex = 0;
      } else {
        mIndex = mSize - 1;
      }

      mSort = sort;
      mCheckSize = mSize;
    }

    @Override
    public boolean hasNext() {
      if (mCheckSize != mSize) {
        throw new IllegalStateException("The list is changed, but not through this iterator, while iterating!");
      }

      if (mSort == SORT_ASC) {
        return mIndex < mSize;
      } else {
        return mIndex >= 0;
      }
    }

    @Override
    public E next() {
      if (mCheckSize != mSize) {
        throw new IllegalStateException("The list is changed, but not through this iterator, while iterating!");
      }
      if (mSort == SORT_ASC) {
        return (E) mData[mIndex++];
      } else {
        return (E) mData[mIndex--];
      }
    }

    @Override
    public void remove() {
      SortedArray.this.remove(mIndex);
      if (mSort == SORT_ASC) {
        mIndex--;
      }
      mCheckSize--;
    }
  }
}
